<template>
	<view class="rich-text-container" :style="containerStyle">
		<!-- 解析后的内容渲染容器 -->
		<view
			class="parsed-content"
			@click="handleClick"
			:class="{ 'markdown-style': type === 'markdown' }"
		></view>
	</view>
</template>

<script>
export default {
	name: 'CustomRichTextParser',
	props: {
		// 内容类型：markdown 或 html
		type: {
			type: String,
			required: true,
			validator: val => ['markdown', 'html'].includes(val),
		},
		// 原始内容
		content: {
			type: String,
			required: true,
			default: '',
		},
		// 容器样式
		containerStyle: {
			type: Object,
			default: () => ({
				padding: '16px',
				fontSize: '16px',
				lineHeight: '1.6',
			}),
		},
		// 是否支持图片预览
		enableImagePreview: {
			type: Boolean,
			default: true,
		},
		// 是否支持链接点击
		enableLink: {
			type: Boolean,
			default: true,
		},
		// 代码高亮主题
		codeTheme: {
			type: String,
			default: 'default',
			validator: val => ['default', 'dark', 'light'].includes(val),
		},
	},
	data() {
		return {
			// 存储所有图片地址，用于预览
			imageUrls: [],
			// 代码高亮样式映射
			codeStyles: {
				default: {
					background: '#f7fafc',
					color: '#2d3748',
					keyword: '#3182ce',
					string: '#2ecc71',
					comment: '#a0aec0',
					function: '#805ad5',
					number: '#e53e3e',
				},
				dark: {
					background: '#2d3748',
					color: '#e2e8f0',
					keyword: '#90cdf4',
					string: '#9ae6b4',
					comment: '#718096',
					function: '#d69e2e',
					number: '#fc8181',
				},
				light: {
					background: '#ffffff',
					color: '#374151',
					keyword: '#005cc5',
					string: '#032f62',
					comment: '#6b7280',
					function: '#6f42c1',
					number: '#d73a49',
				},
			},
		};
	},
	watch: {
		content: {
			immediate: true,
			handler() {
				this.parseContent();
			},
		},
		type: {
			immediate: true,
			handler() {
				this.parseContent();
			},
		},
		codeTheme: {
			immediate: true,
			handler() {
				if (this.type === 'markdown') {
					this.parseContent();
				}
			},
		},
	},
	methods: {
		/**
		 * 解析内容入口方法
		 */
		parseContent() {
			if (!this.content) {
				this.renderContent('<div class="empty-content">无内容</div>');
				return;
			}

			let parsedHtml = '';
			if (this.type === 'markdown') {
				parsedHtml = this.parseMarkdown(this.content);
			} else {
				parsedHtml = this.sanitizeHtml(this.content);
			}

			this.renderContent(parsedHtml);
		},

		/**
		 * 将解析后的HTML渲染到页面
		 */
		renderContent(html) {
			const container = this.$el.querySelector('.parsed-content');
			if (container) {
				container.innerHTML = html;

				// 收集图片地址
				this.collectImageUrls(container);

				// 应用代码高亮样式
				if (this.type === 'markdown') {
					this.applyCodeHighlight(container);
				}
			}
		},

		/**
		 * 收集所有图片地址用于预览
		 */
		collectImageUrls(container) {
			this.imageUrls = [];
			const images = container.querySelectorAll('img');
			images.forEach((img, index) => {
				// 添加图片标识，用于点击时识别
				img.dataset.index = index;
				this.imageUrls.push(img.src);

				// 处理相对路径图片
				if (!img.src.startsWith('http') && !img.src.startsWith('wxfile://')) {
					img.src = this.resolveImagePath(img.src);
				}
			});
		},

		/**
		 * 处理相对路径图片
		 */
		resolveImagePath(src) {
			// 如果是相对路径，尝试转换为绝对路径
			if (src.startsWith('/') && typeof window !== 'undefined') {
				return window.location.origin + src;
			}
			return src;
		},

		/**
		 * 应用代码高亮样式
		 */
		applyCodeHighlight(container) {
			const codeBlocks = container.querySelectorAll('pre.md-code code');
			const styles = this.codeStyles[this.codeTheme];

			// 设置代码块容器样式
			codeBlocks.forEach(block => {
				const pre = block.parentElement;
				pre.style.backgroundColor = styles.background;
				pre.style.color = styles.color;

				// 简单的代码高亮处理
				let code = block.innerHTML;

				// 关键字高亮
				const keywords = [
					'function',
					'if',
					'else',
					'for',
					'while',
					'return',
					'var',
					'let',
					'const',
					'class',
					'extends',
					'import',
					'export',
					'default',
				];
				keywords.forEach(keyword => {
					const regex = new RegExp(`\\b${keyword}\\b`, 'g');
					code = code.replace(
						regex,
						`<span style="color: ${styles.keyword}">${keyword}</span>`
					);
				});

				// 字符串高亮
				code = code.replace(
					/"(.*?)"/g,
					`<span style="color: ${styles.string}">"$1"</span>`
				);
				code = code.replace(
					/'(.*?)'/g,
					`<span style="color: ${styles.string}">'$1'</span>`
				);

				// 注释高亮
				code = code.replace(
					/\/\/(.*?)$/gm,
					`<span style="color: ${styles.comment}">//$1</span>`
				);

				// 函数名高亮
				code = code.replace(
					/function\s+(\w+)/g,
					`function <span style="color: ${styles.function}">$1</span>`
				);

				// 数字高亮
				code = code.replace(
					/\b\d+\b/g,
					`<span style="color: ${styles.number}">$&</span>`
				);

				block.innerHTML = code;
			});
		},

		/**
		 * 处理点击事件（图片预览、链接跳转）
		 */
		handleClick(e) {
			// 处理图片点击
			if (this.enableImagePreview && e.target.tagName === 'IMG') {
				const index = parseInt(e.target.dataset.index);
				uni.previewImage({
					urls: this.imageUrls,
					current: index,
					fail: err => {
						console.error('图片预览失败:', err);
						uni.showToast({
							title: '图片预览失败',
							icon: 'none',
						});
					},
				});
				e.stopPropagation();
				return;
			}

			// 处理链接点击
			if (this.enableLink && e.target.tagName === 'A') {
				e.preventDefault();
				const url = e.target.href;
				this.handleLinkClick(url);
				e.stopPropagation();
				return;
			}
		},

		/**
		 * 处理链接点击
		 */
		handleLinkClick(url) {
			if (!url) return;

			// 内部链接（以/开头）
			if (url.startsWith('/')) {
				uni
					.navigateTo({
						url: url,
					})
					.catch(err => {
						console.error('导航失败:', err);
						uni.showToast({
							title: '无法打开链接',
							icon: 'none',
						});
					});
			}
			// 外部链接
			else if (url.startsWith('http://') || url.startsWith('https://')) {
				uni.showModal({
					title: '打开链接',
					content: url,
					confirmText: '打开',
					cancelText: '取消',
					success: res => {
						if (res.confirm) {
							uni.openURL({
								url: url,
								fail: err => {
									console.error('打开链接失败:', err);
									uni.showToast({
										title: '打开链接失败',
										icon: 'none',
									});
								},
							});
						}
					},
				});
			} else {
				uni.showToast({
					title: '不支持的链接格式',
					icon: 'none',
				});
			}
		},

		/**
		 * Markdown解析核心方法
		 */
		parseMarkdown(markdown) {
			let html = markdown;

			// 解析标题
			html = html.replace(/^(#{1,6})\s+(.*?)$/gm, (match, level, text) => {
				const headingLevel = Math.min(level.length, 6);
				return `<h${headingLevel} class="md-heading md-heading-${headingLevel}">${text}</h${headingLevel}>`;
			});

			// 解析粗体
			html = html.replace(
				/\*\*(.*?)\*\*/g,
				'<strong class="md-bold">$1</strong>'
			);
			html = html.replace(/__(.*?)__/g, '<strong class="md-bold">$1</strong>');

			// 解析斜体
			html = html.replace(/\*(.*?)\*/g, '<em class="md-italic">$1</em>');
			html = html.replace(/_(.*?)_/g, '<em class="md-italic">$1</em>');

			// 解析链接
			html = html.replace(
				/\[([^\]]+)\]\(([^)]+)\)/g,
				'<a href="$2" class="md-link">$1</a>'
			);

			// 解析图片
			html = html.replace(
				/!\[(.*?)\]\(([^)]+)\)/g,
				'<img src="$2" alt="$1" class="md-image" />'
			);

			// 解析无序列表和有序列表
			html = this.parseLists(html);

			// 解析代码块
			html = html.replace(/```([\s\S]*?)```/g, (match, code) => {
				return `<pre class="md-code"><code>${this.escapeHtml(
					code
				)}</code></pre>`;
			});

			// 解析行内代码
			html = html.replace(/`(.*?)`/g, '<code class="md-code-inline">$1</code>');

			// 解析引用块
			html = html.replace(
				/^> (.*?)$/gm,
				'<blockquote class="md-quote">$1</blockquote>'
			);

			// 解析水平分割线
			html = html.replace(/^---$/gm, '<hr class="md-hr" />');

			// 解析段落
			html = html.replace(
				/^(?!<h|<ul|<ol|<pre|<p|<blockquote)(.*?)$/gm,
				match => {
					if (match.trim()) return `<p class="md-paragraph">${match}</p>`;
					return match;
				}
			);

			// 解析换行
			html = html.replace(/\n/g, '<br>');

			return html;
		},

		/**
		 * 解析列表（有序和无序）
		 */
		parseLists(html) {
			// 先处理无序列表
			html = html.replace(/^- (.*?)$/gm, '<li class="md-list-item">$1</li>');
			// 再处理有序列表
			html = html.replace(
				/^[0-9]+\. (.*?)$/gm,
				'<li class="md-list-item">$1</li>'
			);

			// 包裹列表项
			html = html.replace(
				/(<li class="md-list-item">.*?<\/li>)(?=<li|$)/gs,
				match => {
					// 判断是否是有序列表（基于是否包含数字开头）
					if (/^\d+\./.test(match)) {
						return `<ol class="md-list md-ordered-list">${match}</ol>`;
					} else {
						return `<ul class="md-list md-unordered-list">${match}</ul>`;
					}
				}
			);

			return html;
		},

		/**
		 * 净化HTML，防止XSS攻击，只保留安全标签
		 */
		sanitizeHtml(html) {
			// 允许的标签和属性
			const allowedTags = {
				p: ['class', 'style'],
				div: ['class', 'style'],
				span: ['class', 'style'],
				a: ['href', 'class', 'target'],
				img: ['src', 'alt', 'class', 'width', 'height'],
				strong: ['class'],
				em: ['class'],
				h1: ['class'],
				h2: ['class'],
				h3: ['class'],
				h4: ['class'],
				h5: ['class'],
				h6: ['class'],
				ul: ['class'],
				ol: ['class'],
				li: ['class'],
				br: [],
				pre: ['class'],
				code: ['class'],
				blockquote: ['class'],
				hr: ['class'],
			};

			// 移除不允许的标签
			let sanitized = html;

			// 匹配所有HTML标签
			sanitized = sanitized.replace(/<\/?[^>]+(>|$)/g, tag => {
				// 提取标签名
				const tagNameMatch = tag.match(/^<\/?([a-zA-Z0-9]+)/);
				if (!tagNameMatch) return '';

				const tagName = tagNameMatch[1].toLowerCase();

				// 检查是否是允许的标签
				if (!allowedTags.hasOwnProperty(tagName)) {
					return ''; // 移除不允许的标签
				}

				// 处理闭合标签
				if (tag.startsWith('</')) {
					return `</${tagName}>`;
				}

				// 处理自闭合标签
				if (tag.endsWith('/>')) {
					return `<${tagName}${this.extractAllowedAttributes(
						tag,
						allowedTags[tagName]
					)}/>`;
				}

				// 处理开始标签
				return `<${tagName}${this.extractAllowedAttributes(
					tag,
					allowedTags[tagName]
				)}>`;
			});

			return sanitized;
		},

		/**
		 * 提取标签中允许的属性
		 */
		extractAllowedAttributes(tag, allowedAttributes) {
			let attributes = '';

			// 匹配所有属性
			const attrMatches = tag.match(/\s+[a-zA-Z0-9-]+="[^"]*"/g);
			if (attrMatches) {
				attrMatches.forEach(attr => {
					// 提取属性名
					const attrNameMatch = attr.match(/([a-zA-Z0-9-]+)=/);
					if (attrNameMatch && allowedAttributes.includes(attrNameMatch[1])) {
						attributes += attr;
					}
				});
			}

			return attributes;
		},

		/**
		 * 转义HTML特殊字符
		 */
		escapeHtml(str) {
			return str
				.replace(/&/g, '&amp;')
				.replace(/</g, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/"/g, '&quot;')
				.replace(/'/g, '&#039;');
		},
	},
};
</script>

<style scoped>
.rich-text-container {
	width: 100%;
	box-sizing: border-box;
	color: #333;
}

.parsed-content {
	width: 100%;
}

/* 空内容样式 */
.empty-content {
	text-align: center;
	padding: 40px 0;
	color: #999;
}

/* Markdown基础样式 */
.markdown-style {
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
		Arial, sans-serif;
}

/* 标题样式 */
.md-heading {
	margin: 1.5em 0 0.5em;
	font-weight: 600;
	color: #2d3748;
}

.md-heading-1 {
	font-size: 1.8em;
	border-bottom: 1px solid #e2e8f0;
	padding-bottom: 0.5em;
}

.md-heading-2 {
	font-size: 1.5em;
	border-bottom: 1px solid #e2e8f0;
	padding-bottom: 0.5em;
}

.md-heading-3 {
	font-size: 1.25em;
}

.md-heading-4,
.md-heading-5,
.md-heading-6 {
	font-size: 1.1em;
}

/* 段落样式 */
.md-paragraph {
	margin: 1em 0;
}

/* 粗体和斜体 */
.md-bold {
	font-weight: 700;
}

.md-italic {
	font-style: italic;
}

/* 列表样式 */
.md-list {
	margin: 1em 0;
	padding-left: 2em;
}

.md-unordered-list {
	list-style-type: disc;
}

.md-ordered-list {
	list-style-type: decimal;
}

.md-list-item {
	margin: 0.5em 0;
}

/* 链接样式 */
.md-link {
	color: #3182ce;
	text-decoration: none;
}

.md-link:hover {
	text-decoration: underline;
}

/* 图片样式 */
.md-image {
	max-width: 100%;
	height: auto;
	margin: 1.5em 0;
	border-radius: 4px;
	cursor: pointer;
}

/* 引用块样式 */
.md-quote {
	border-left: 4px solid #e2e8f0;
	padding-left: 1em;
	color: #718096;
	margin: 1em 0;
	display: block;
}

/* 水平分割线 */
.md-hr {
	border: 0;
	border-top: 1px solid #e2e8f0;
	margin: 2em 0;
}

/* 代码样式 */
.md-code {
	padding: 1em;
	border-radius: 4px;
	overflow-x: auto;
	margin: 1.5em 0;
	font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
		'Courier New', monospace;
	font-size: 0.9em;
}

.md-code-inline {
	padding: 0.2em 0.4em;
	border-radius: 3px;
	font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
		'Courier New', monospace;
	font-size: 0.9em;
	background-color: #f7fafc;
}
</style>
